package com.cblz.bd.common.core.utils;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cblz.bd.common.core.constant.BdAdminConstant;
import com.cblz.bd.common.core.entity.system.QueryRequest;
import com.cblz.bd.common.core.exception.BdAdminException;
import io.vavr.Tuple;
import io.vavr.Tuple3;
import io.vavr.Tuple4;
import io.vavr.Tuple5;
import org.apache.commons.lang3.StringUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import static io.vavr.API.*;


/**
 * {@code @Name:} SearchParamsUtil
 * {@code @Description: } 前端通用搜索参数处理工具类
 * {@code @Author:} by yanjieping
 * {@code @CreateDate:} 2022/11/15 - 周二
 */
public class SearchParamsUtil {

    /**
     * 查询数据处理
     *
     * @param searchParams
     * @return
     */
    @Deprecated
    public static Tuple4 searchParamsHandle(Map<String, Object> searchParams) {
        return Tuple.of(searchParams.get("search"), searchParams.get("page"), searchParams.get("limit"), searchParams.get("quick_search"));
    }

    /**
     * 通用查询参数转换到指定实体类
     *
     * @param resultClass 实体类
     * @param search      通用查询参数
     * @param <R>         实体类Class
     * @return resultClass
     * @throws Exception 所有异常
     */
    @Deprecated
    public static <R> R ParamsFormat2Class(R resultClass, List<LinkedHashMap<String, Object>> search, Map<String, String> quickSearch) throws Exception {
        if (Boolean.TRUE.equals(isNull(search))) {
            if (null != quickSearch && !quickSearch.isEmpty()) {
                // 反射获取对应的列，再将值赋给列
                Class<?> aClass = resultClass.getClass();
                Object object = aClass.getDeclaredConstructor().newInstance();
                // 拿到父类私有列
                Field[] superClassDeclaredFields = aClass.getSuperclass().getDeclaredFields();
                Field[] declaredFields = aClass.getDeclaredFields();

                Field[] allFields = ArrayUtil.addAll(superClassDeclaredFields, declaredFields);

                String selectFields = quickSearch.get("selectFields");
                String keyword = quickSearch.get("keyword");

                Arrays.stream(StringUtils.split(selectFields, BdAdminConstant.SYSTEM_COMMA.getVal())).forEach(s -> {
                    for (Field declaredField : allFields) {
                        if (StringUtils.equals(s, declaredField.getName())) {
                            try {
                                declaredField.setAccessible(true);
                                declaredField.set(object, keyword);
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                });
                return (R) object;
            }
            return resultClass;
        } else {
            // 反射获取对应的列，再将值赋给列
            Class<?> aClass = resultClass.getClass();

            Object object = aClass.getDeclaredConstructor().newInstance();
            // 拿到私有列
            Field[] declaredFields = aClass.getDeclaredFields();
            // 上一个列
            AtomicReference<String> lastField = new AtomicReference<>();

            search.forEach(listValue -> listValue.forEach((key, value) -> {
                Match(key).of(Case($("field"), () -> {
                            // 防止参数中传入Int等数字类型的值，否则会出 ClassCastException
                            var val = value.toString();
                            lastField.set(val);
                            return val;
                        }), Case($("val"), () -> {
                            // 防止参数中传入Int等数字类型的值，否则会出 ClassCastException
                            var val = value.toString();
                            // 根据上一个列名来找到对应的列
                            for (Field declaredField : declaredFields) {
                                if (StringUtils.equals(declaredField.getName(), lastField.get())) {
                                    try {
                                        declaredField.setAccessible(true);
                                        String typeName = declaredField.getType().getSimpleName();
                                        if (StringUtils.endsWith(lastField.get(), "time")) {
                                            String[] time = StringUtils.split(val, BdAdminConstant.SYSTEM_COMMA.getVal());
                                            Field startTime = aClass.getDeclaredField(lastField.get() + "StartTime");
                                            Field endTime = aClass.getDeclaredField(lastField.get() + "EndTime");
                                            startTime.setAccessible(true);
                                            endTime.setAccessible(true);

                                            DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                                            startTime.set(object, LocalDateTime.parse(time[0], dateTimeFormatter).toEpochSecond(ZoneOffset.of("+8")));
                                            endTime.set(object, LocalDateTime.parse(time[1], dateTimeFormatter).toEpochSecond(ZoneOffset.of("+8")));

                                        } else {
                                            Serializable serializable = Match(typeName).of(Case($("String"), () -> val), Case($("Integer"), () -> Integer.parseInt(val)), Case($("Double"), () -> Double.parseDouble(val)), Case($("Long"), () -> Long.parseLong(val)), Case($("Boolean"), () -> Boolean.parseBoolean(val)));
                                            declaredField.set(object, serializable);
                                        }
                                    } catch (IllegalAccessException e) {
                                        throw new RuntimeException(e);
                                    } catch (NoSuchFieldException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            }
                            return val;
                        }),
                        // 不做处理
                        Case($("operator"), () -> value),
                        // 不做处理
                        Case($("render"), () -> value));

            }));

            return (R) object;
        }
    }

    /**
     * 判断参数集合是否是空的
     *
     * @param searchList 参数集合
     * @return 空是True，非空为False
     */
    public static Boolean isNull(List<LinkedHashMap<String, Object>> searchList) {
        return searchList.isEmpty();
    }

    /**
     * 查询数据处理（优化版）
     *
     * @param searchParams 查询参数：格式--
     *                     {
     *                     *                     search:[
     *                     *                        {"field" : "属性"},{"val" : "查询值"},{"operator" : "查询操作"},
     *                     *                        {"field" : "属性"},{"val" : "查询值"},{"operator" : "查询操作"}
     *                     *                     ],
     *                     *                     order: "属性,desc或者asc",
     *                     *                     quick_search: {
     *                     *                         "selectFields": "属性名,属性名",  #多个查询属性用逗号拼接
     *                     *                         "keyword": "值"
     *                     *                     }
     *                     }
     * @return
     */
    public static Tuple5 searchParamsHandle2(Map<String, Object> searchParams) {
        return Tuple.of(searchParams.get("search"), searchParams.get("page"), searchParams.get("limit"), searchParams.get("quick_search"), searchParams.get("order"));
    }

    /**
     * 数据处理，将参数转换成QueryWrapper条件构造器条件  (优化版)
     *
     * @param entityClass 查询表的对应实体类或者DTO
     * @param params      前端传入的参数元祖
     * @param <R>         查询表的对应实体类或者DTO
     * @return
     */
    public static <R, T> Tuple3<Page<R>, QueryWrapper<R>, T> params2QueryWrapper(R entityClass, Tuple5 params) {
        return params2QueryWrapper(entityClass, null, params);
    }

    /**
     * 数据处理，将参数转换成QueryWrapper条件构造器条件  (优化版)
     *
     * @param entityClass 查询表的对应实体类或者DTO
     * @param params      前端传入的参数元祖
     * @param <R>         查询表的对应实体类或者DTO
     * @return 条件构造器
     */
    public static <R, T> Tuple3<Page<R>, QueryWrapper<R>, T> params2QueryWrapper(R entityClass, T dtoClass, Tuple5 params) {
        QueryRequest queryRequest = new QueryRequest();
        var search = (List<LinkedHashMap<String, Object>>) params._1;
        // 关联数据的字段，根据字段信息查询关联表的相关信息
        var associatedMap = search.stream().filter((key) -> key.containsKey(BdAdminConstant.SEARCH_ASSOCIATED.getVal())).peek(key -> MapUtil.removeAny(key, BdAdminConstant.SEARCH_OPERATOR.getVal(), BdAdminConstant.SEARCH_ASSOCIATED.getVal())).toList();
        // 非关联数据的字段，可以直接拼接条件查询
        var notAssociatedMap = new ArrayList<>(search.stream().filter((key) -> !key.containsKey(BdAdminConstant.SEARCH_ASSOCIATED.getVal())).toList());
        var page = Integer.parseInt(Objects.isNull(params._2) ? String.valueOf(queryRequest.getPageNum()) : params._2.toString());
        var limit = Integer.parseInt(Objects.isNull(params._3) ? String.valueOf(queryRequest.getPageSize()) : params._3.toString());
        var quickSearch = Objects.isNull(params._4) ? null : (Map<String, String>) params._4;
        var order = Objects.isNull(params._5) ? null : params._5.toString();

        // 构建条件构造器
        QueryWrapper<R> queryWrapper = new QueryWrapper<>();
        // 反射获取实体类
        Field[] fields;
        Object object;
        AtomicReference<String> lastField = new AtomicReference<>("");
        AtomicReference<Long> rangeStart = new AtomicReference<>(0L);
        AtomicReference<Long> rangeEnd = new AtomicReference<>(0L);
        // 判断dto是否为实体类的子孙类
        if (Objects.nonNull(dtoClass) && entityClass.getClass().isAssignableFrom(dtoClass.getClass())) {
            fields = ReflectUtil.getFields(dtoClass.getClass());
            object = ReflectUtil.newInstance(dtoClass.getClass());
        } else {
            fields = ReflectUtil.getFields(entityClass.getClass());
            object = ReflectUtil.newInstance(entityClass.getClass());
        }
        notAssociatedMap.addAll(associatedMap);

        if (!notAssociatedMap.isEmpty()) {
            search.forEach(searchValue -> searchValue.forEach((key, value) -> {
                // 防止参数中传入Int等数字类型的值，否则会出 ClassCastException
                var val = value.toString();
                Match(key).of(Case($(BdAdminConstant.SEARCH_FIELD.getVal()), () -> {
                    lastField.set(val);
                    return val;
                }), Case($(BdAdminConstant.SEARCH_VAL.getVal()), () -> {
                    Arrays.stream(fields).forEach(field -> {
                        field.setAccessible(true);
                        try {
                            if (StringUtils.equals(field.getName(), lastField.get())) {
                                String typeName = field.getType().getSimpleName();
                                // 区间查询,那就不需要存储到对象中去
                                if (Objects.nonNull(searchValue.get(BdAdminConstant.SEARCH_OPERATOR.getVal())) && StringUtils.equals(StringUtils.lowerCase(searchValue.get(BdAdminConstant.SEARCH_OPERATOR.getVal()).toString()), BdAdminConstant.SEARCH_OPERATOR_RANGE.getVal())) {
                                    // 如果是字段是时间
                                    if (StringUtils.endsWith(field.getName(), "time")) {
                                        String[] timeSplit = StringUtils.split(val, BdAdminConstant.SYSTEM_COMMA.getVal());
                                        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

                                        rangeStart.set(LocalDateTime.parse(timeSplit[0], dateTimeFormatter).toEpochSecond(ZoneOffset.of("+8")));
                                        rangeEnd.set(LocalDateTime.parse(timeSplit[1], dateTimeFormatter).toEpochSecond(ZoneOffset.of("+8")));
                                    } else {
                                        String[] timeSplit = StringUtils.split(val, BdAdminConstant.SYSTEM_COMMA.getVal());
                                        if (timeSplit.length == 1)
                                            throw new BdAdminException("请输入完整的范围值");
                                        rangeStart.set(Long.parseLong(timeSplit[0]));
                                        rangeEnd.set(Long.parseLong(timeSplit[1]));
                                    }
                                } else {
                                    Serializable serializable = Match(StringUtils.lowerCase(typeName)).of(Case($("object"), () -> val), Case($("string"), () -> val), Case($("integer"), () -> Integer.parseInt(val)), Case($("double"), () -> Double.parseDouble(val)), Case($("long"), () -> Long.parseLong(val)), Case($("boolean"), () -> Boolean.parseBoolean(val)));
                                    field.set(object, serializable);
                                }
                            }
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    });
                    return val;
                }), Case($(BdAdminConstant.SEARCH_OPERATOR.getVal()), () -> {
                    Match(StringUtils.lowerCase(val)).of(
                            Case($(BdAdminConstant.SEARCH_OPERATOR_EQ.getVal()), () -> {
                                queryWrapper.eq(lastField.get(), ReflectUtil.getFieldValue(object, lastField.get()));
                                return val;
                            }), Case($(BdAdminConstant.SEARCH_OPERATOR_NE.getVal()), () -> {
                                queryWrapper.ne(lastField.get(), ReflectUtil.getFieldValue(object, lastField.get()));
                                return val;
                            }), Case($(BdAdminConstant.SEARCH_OPERATOR_GT.getVal()), () -> {
                                queryWrapper.gt(lastField.get(), ReflectUtil.getFieldValue(object, lastField.get()));
                                return val;
                            }), Case($(BdAdminConstant.SEARCH_OPERATOR_GE.getVal()), () -> {
                                queryWrapper.ge(lastField.get(), ReflectUtil.getFieldValue(object, lastField.get()));
                                return val;
                            }), Case($(BdAdminConstant.SEARCH_OPERATOR_LT.getVal()), () -> {
                                queryWrapper.lt(lastField.get(), ReflectUtil.getFieldValue(object, lastField.get()));
                                return val;
                            }), Case($(BdAdminConstant.SEARCH_OPERATOR_LE.getVal()), () -> {
                                queryWrapper.le(lastField.get(), ReflectUtil.getFieldValue(object, lastField.get()));
                                return val;
                            }), Case($(BdAdminConstant.SEARCH_OPERATOR_LIKE.getVal()), () -> {
                                queryWrapper.like(lastField.get(), ReflectUtil.getFieldValue(object, lastField.get()));
                                return val;
                            }), Case($(BdAdminConstant.SEARCH_OPERATOR_RANGE.getVal()), () -> {
                                queryWrapper.between(lastField.get(), rangeStart.get(), rangeEnd.get());
                                rangeStart.set(0L);
                                rangeEnd.set(0L);
                                return val;
                            }));
                    return val;
                }), Case($(BdAdminConstant.SEARCH_RENDER.getVal()), () -> value));
            }));
        }
        if (!Objects.isNull(quickSearch) && !quickSearch.isEmpty()) {
            String selectFields = quickSearch.get("selectFields");
            String keyword = quickSearch.get("keyword");

            Arrays.stream(StringUtils.split(selectFields, BdAdminConstant.SYSTEM_COMMA.getVal())).forEach(s -> queryWrapper.like(s, keyword));
        }
        if (!Objects.isNull(order) && StringUtils.isNotBlank(order)) {
            String[] splitOrderParams = order.split(BdAdminConstant.SYSTEM_COMMA.getVal());
            if (StringUtils.equals(splitOrderParams[1], "desc")) {
                queryWrapper.orderByDesc(splitOrderParams[0]);
            } else {
                queryWrapper.orderByAsc(splitOrderParams[0]);
            }
        }
        return Tuple(new Page<R>(page, limit), queryWrapper, (T) object);
    }

}
